package com.imooc.bilibili.service;


import com.imooc.bilibili.dao.VideoDao;
import com.imooc.bilibili.domain.*;
import com.imooc.bilibili.exception.ConditionException;
import com.imooc.bilibili.util.FastDFSUtil;
import com.imooc.bilibili.util.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import eu.bitwalker.useragentutils.UserAgent;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;

@Service
public class VideoService {

    @Autowired
    private VideoDao videoDao;

    @Autowired
    private FastDFSUtil fastDFSUtil;

    @Autowired
    private UserCoinService userCoinService;

    @Autowired
    private UserService userService;

    /**
     * @param video
     * @Transactional 涉及到两次数据库的添加，所以需要增加事务的处理，保持原子性
     */
    @Transactional
    public void addVideos(Video video) {
        Date now = new Date();
        video.setCreateTime(now);
        videoDao.addVideos(video);
        Long videoId = video.getId();
        // 获取该视频的标签列表，依次进行添加
        List<VideoTag> tagList = video.getVideoTagList();
        for (VideoTag videoTag : tagList) {
            videoTag.setCreateTime(now);
            videoTag.setVideoId(videoId);
        }
        videoDao.batchAddVideoTags(tagList);
    }

    public PageResult<Video> pageListVideos(Integer size, Integer no, String area) {
        if (size == null || no == null) {
            throw new ConditionException("参数异常！");
        }
        // 封装传进来的参数
        Map<String, Object> params = new HashMap<>();
        // 从数据库的第几行开始获取数据
        params.put("start", (no - 1) * size);
        // 每次要查询多少条数据
        params.put("limit", size);
        params.put("area", area);
        List<Video> list = new ArrayList<>();
        // 查询满足筛选条件的数据一共有多少条
        Integer total = videoDao.pageCountVideos(params);
        if (total > 0) {
            // 分页查询满足条件的数据
            list = videoDao.pageListVideos(params);
        }
        return new PageResult<>(total, list);
    }

    public void viewVideoOnlineBySlices(HttpServletRequest request, HttpServletResponse response, String url) throws Exception {
        fastDFSUtil.viewVideoOnlineBySlices(request, response, url);
    }

    /**
     * 点赞视频
     *
     * @param videoId 当前被点赞的视频
     * @param userId  当前给这个视频点赞的用户
     */
    public void addVideoLike(Long videoId, Long userId) {
        Video video = videoDao.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        // 判断当前这个视频有没有被当前登录这个用户点赞过
        VideoLike videoLike = videoDao.getVideoLikeByVideoIdAndUserId(videoId, userId);
        if (videoLike != null) {
            throw new ConditionException("已经点赞过！");
        }
        videoLike = new VideoLike();
        videoLike.setVideoId(videoId);
        videoLike.setUserId(userId);
        videoLike.setCreateTime(new Date());
        videoDao.addVideoLike(videoLike);
    }

    /**
     * 取消点赞视频
     *
     * @param videoId
     * @param userId
     */
    public void deleteVideoLike(Long videoId, Long userId) {
        videoDao.deleteVideoLike(videoId, userId);
    }

    /**
     * 获取当前视频被点赞的数量
     *
     * @param videoId
     * @param userId
     * @return
     */
    public Map<String, Object> getVideoLikes(Long videoId, Long userId) {
        // 通过视频id查询当前视频被点赞的数量
        Long count = videoDao.getVideoLikes(videoId);
        // 判断当前这个视频有没有被当前登录这个用户点赞过
        VideoLike videoLike = videoDao.getVideoLikeByVideoIdAndUserId(videoId, userId);
        // 如果当前用户点赞过该视频，like为true，否则like为false
        boolean like = videoLike != null ? true : false;
        HashMap<String, Object> result = new HashMap<>();
        // 当前视频被点赞的数量
        result.put("count", count);
        // 如果当前用户已经登录，存储当前用户是否点赞过该视频(前端页面显示用的)
        result.put("like", like);
        return result;
    }

    /**
     * 收藏当前视频
     *
     * @param videoCollection
     * @param userId
     */
    public void addVideoCollection(VideoCollection videoCollection, Long userId) {
        // 获取视频id
        Long videoId = videoCollection.getVideoId();
        // 获取收藏分组id
        Long groupId = videoCollection.getGroupId();
        if (videoId == null || groupId == null) {
            throw new ConditionException("参数异常！");
        }
        // 通过视频id获取当前视频
        Video video = videoDao.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        // 先删除后添加操作，就可以包含添加数据和更新数据，这样子一举两得
        // 删除原有视频收藏
        videoDao.deleteVideoCollection(videoId, userId);
        // 添加新的视频收藏
        videoCollection.setUserId(userId);
        videoCollection.setCreateTime(new Date());
        videoDao.addVideoCollection(videoCollection);
    }

    /**
     * 取消收藏视频
     *
     * @param videoId
     * @param userId
     */
    public void deleteVideoCollection(Long videoId, Long userId) {
        videoDao.deleteVideoCollection(videoId, userId);
    }

    /**
     * 获取当前视频被收藏的数量
     *
     * @param videoId
     * @param userId
     * @return
     */
    public Map<String, Object> getVideoCollections(Long videoId, Long userId) {
        // 通过视频id查询当前视频被收藏的数量
        Long count = videoDao.getVideoCollections(videoId);
        // 判断当前这个视频有没有被当前登录这个用户收藏过
        VideoCollection videoCollection = videoDao.getVideoCollectionByVideoIdAndUserId(videoId, userId);
        // 如果当前用户收藏过该视频，返回true，否则返回false
        boolean like = videoCollection != null ? true : false;
        Map<String, Object> result = new HashMap<>();
        // 当前视频被收藏的数量
        result.put("count", count);
        // 如果当前用户已经登录，存储当前用户是否收藏过该视频(前端页面显示用的)
        result.put("like", like);
        return result;
    }

    /**
     * 给当前视频进行投币
     * 注意：在同一个方法里对多表进行操作，需要添加事务处理(@Transactional)
     *
     * @param videoCoin
     * @param userId
     */
    @Transactional
    public void addVideoCoins(VideoCoin videoCoin, Long userId) {
        // 获取当前投币的对应视频
        Long videoId = videoCoin.getVideoId();
        // 获取当前用户要给该视频进行投币的数量
        Integer amount = videoCoin.getAmount();
        if (videoId == null) {
            throw new ConditionException("参数异常！");
        }
        Video video = videoDao.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        // 查询当前登录用户是否拥有足够的硬币
        Integer userCoinsAmount = userCoinService.getUserCoinsAmount(userId);
        // userCoinsAmount 为空表示当前用户没有硬币数量
        userCoinsAmount = userCoinsAmount == null ? 0 : userCoinsAmount;
        // 如果当前用户要给视频投币的数量 > 当前用户拥有的硬币总数
        if (amount > userCoinsAmount) {
            throw new ConditionException("硬币数量不足！");
        }
        // 查询当前登录用户已经对该视频投了多少个硬币
        VideoCoin dbVideoCoin = videoDao.getVideoCoinByVideoIdAndUserId(videoId, userId);
        // 新增视频投币
        if (dbVideoCoin == null) { // 表示当前用户首次对该视频进行投币
            videoCoin.setUserId(userId);
            videoCoin.setCreateTime(new Date());
            videoDao.addVideoCoin(videoCoin);
        } else { // 表示当前用户之前已经对该视频进行过投币了，就修改投币记录信息即可
            Integer dbAmount = dbVideoCoin.getAmount();
            dbAmount += amount;
            // 更新视频投币记录
            videoCoin.setUserId(userId);
            videoCoin.setAmount(dbAmount);
            videoCoin.setUpdateTime(new Date());
            videoDao.updateVideoCoin(videoCoin);
        }
        // 更新当前用户持有的硬币总数
        userCoinService.updateUserCoinsAmount(userId, (userCoinsAmount - amount));
    }

    public Map<String, Object> getVideoCoins(Long videoId, Long userId) {
        // 获取当前视频被投币的数量
        Long count = videoDao.getVideoCoinsAmount(videoId);
        VideoCoin videoCollection = videoDao.getVideoCoinByVideoIdAndUserId(videoId, userId);
        // 如果当前用户给该视频投过币，返回true，否则返回false
        boolean like = videoCollection != null ? true : false;
        Map<String, Object> result = new HashMap<>();
        result.put("count", count);
        result.put("like", like);
        return result;
    }

    /**
     * 添加视频评论
     *
     * @param videoComment
     * @param userId
     */
    public void addVideoComment(VideoComment videoComment, Long userId) {
        Long videoId = videoComment.getVideoId();
        if (videoId == null) {
            throw new ConditionException("参数异常！");
        }
        Video video = videoDao.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        videoComment.setUserId(userId);
        videoComment.setCreateTime(new Date());
        videoDao.addVideoComment(videoComment);
    }

    /**
     * 分页查询视频评论
     * 下面涉及的一级评论，也就是根评论
     *
     * @param size
     * @param no
     * @param videoId
     * @return
     */
    public PageResult<VideoComment> pageListVideoComments(Integer size, Integer no, Long videoId) {
        Video video = videoDao.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        // -------------- 分页参数的设置 ---------------------
        HashMap<String, Object> params = new HashMap<>();
        // 从数据库的第几行开始获取数据
        params.put("start", (no - 1) * size);
        // 每次要查询多少条数据
        params.put("limit", size);
        params.put("videoId", videoId);
        // --------------------------------------------------

        // 查询一级评论的总数量
        Integer total = videoDao.pageCountVideoComments(params);
        // 一级评论相关信息的列表
        List<VideoComment> list = new ArrayList<>();
        if (total > 0) {
            // 查询一级评论相关信息的列表
            list = videoDao.pageListVideoComments(params);

            // ---------------- 批量查询二级评论 --------------------
            // 一级评论id的集合
            List<Long> parentIdList = null;
            // 遍历一级评论相关信息的列表，获取一级评论id的集合
            for (VideoComment videoComment : list) {
                parentIdList.add(videoComment.getId());
            }
            // 根据一级评论id的集合去查询所有的二级评论相关信息的集合
            List<VideoComment> childCommentsList = videoDao.batchGetVideoCommentsByRootIds(parentIdList);

            // ------------------- 批量查询用户信息 --------------------
            // 创建评论的用户id的集合
            Set<Long> userIdList = null;
            // 遍历一级评论相关信息的列表，获取创建评论的用户id的集合
            for (VideoComment videoComment : list) {
                userIdList.add(videoComment.getUserId());
            }

            // 回复评论的用户id的集合
            Set<Long> replyUserIdList = null;
            // 遍历二级评论相关信息的集合，获取回复评论的用户id的集合
            for (VideoComment videoComment : childCommentsList) {
                replyUserIdList.add(videoComment.getReplyUserId());
            }

            // 将投稿视频的用户id的集合 与 回复评论的用户id的集合 进行合并
            // 统一成一个完整的所有userId的集合
            userIdList.addAll(replyUserIdList);

            // 根据用户id，批量查询用户相关信息
            List<UserInfo> userInfoList = userService.batchGetUserInfoByUserIds(userIdList);
            // userId -> userInfo
            Map<Long, UserInfo> userInfoMap = new HashMap<>();
            for (UserInfo userInfo : userInfoList) {
                userInfoMap.put(userInfo.getUserId(), userInfo);
            }

            // 遍历一级评论相关信息的列表
            for (VideoComment comment : list) {
                Long id = comment.getId();
                // 存储二级评论(子评论)相关的信息
                List<VideoComment> childList = new ArrayList<>();
                for (VideoComment child : childCommentsList) {
                    // 如果二级评论的根评论id 等于 一级评论的id
                    if (id.equals(child.getRootId())) {
                        // 创建这条二级评论的用户的相关信息
                        child.setUserInfo(userInfoMap.get(child.getUserId()));
                        // 二级评论回复的这条一级评论的用户的相关信息
                        child.setReplyUserInfo(userInfoMap.get(child.getReplyUserInfo()));
                        childList.add(child);
                    }
                }
                // 将二级评论相关的信息 存储到 一级评论相关信息里面
                comment.setChildList(childList);
                // 设置一级评论本身的用户信息
                comment.setUserInfo(userInfoMap.get(comment.getUserId()));
            }
        }
        return new PageResult<>(total, list);
    }

    public Map<String, Object> getVideoDetails(Long videoId) {
        // 根据视频id查询相关的视频信息
        Video video = videoDao.getVideoDetails(videoId);
        // 获取投稿该视频的用户id
        Long userId = video.getUserId();
        // 获取投稿该视频的用户的相关信息
        User user = userService.getUserInfo(userId);
        UserInfo userInfo = user.getUserInfo();
        Map<String, Object> result = new HashMap<>();
        result.put("video", video);
        result.put("userInfo", userInfo);
        return result;
    }

    /**
     * 添加视频观看记录
     *
     * @param videoView
     * @param request
     */
    public void addVideoView(VideoView videoView, HttpServletRequest request) {
        // 获取用户id
        Long userId = videoView.getUserId();
        // 获取视频id
        Long videoId = videoView.getVideoId();
        // 生成clientId
        String agent = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(agent);
        // 获取客户端id
        String clientId = String.valueOf(userAgent.getId());
        // 获取客户端ip
        String ip = IpUtil.getIP(request);
        // 构建请求集合，封装多个请求参数
        HashMap<String, Object> params = new HashMap<>();
        // 区分登录用户和游客：如果是用户就具有用户id，如果是游客则具有客户端ip和客户端id
        if (userId != null) {
            // 如果是用户登录模式，则需要添加用户id
            params.put("userId", userId);
        } else {
            // 如果是游客模式，则需要添加客户端ip和客户端id
            params.put("ip", ip);
            params.put("clientId", clientId);
        }
        // 日期处理
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 将当天这个时间参数添加到请求集合中
        params.put("today", sdf.format(now));
        // 添加视频id到请求集合中
        params.put("videoId", videoId);
        // 获取观看记录
        VideoView dbVideoView = videoDao.getVideoView(params);
        // 如果观看记录为空，才进行观看记录的添加
        // 如果不为空，说明当天已经有这一条记录了，不能再进行添加了
        if (dbVideoView == null) {
            videoView.setIp(ip);
            videoView.setClientId(clientId);
            videoView.setCreateTime(new Date());
            videoDao.addVideoView(videoView);
        }
    }

    /**
     * 查询视频播放量
     *
     * @param videoId
     * @return
     */
    public Integer getVideoViewCounts(Long videoId) {
        return videoDao.getVideoViewCounts(videoId);
    }
}
